
/*
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  The four essential freedoms with GNU GPL software:
 *  The freedom to run the program, for any purpose
 *  The freedom to study how the program works, and change it to make it do what you wish
 *  The freedom to redistribute copies so you can help your girlfriends and friends
 *  The freedom to distribute copies of your modified versions to your girlfriends and friends
 *
 *   ,           ,
 *  /             \
 * ((__-^^-,-^^-__))
 * `-_---'  `---_-'
 *  `--|o`   'o|--'
 *      \  `  /
 *       ): :(
 *       :o_o:
 *        "-"
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "main.h"
#include "mpool.h"
#include "misc.h"

#define mywrapped_malloc mgr_malloc
#define mywrapped_free mgr_free
#define mywrapped_memset memset

static int debug = 0;

/*
 * memory pool subsystem
 * 'mem' is reserved prefix in c standard definition.
 *
 * create new pool
 * struct mpool *mp = mpool_pool_new ();
 *
 * malloc in a pool, resized automatically
 * void *p = mpool_pool_malloc ((struct mpool *)pool, (size_t)size);
 *
 * delete a given pool, do not create new empy one.
 * (void) mpool_pool_delete ((struct mpool *)pool);
 *
 * check mpool routimes use valgrind software
 * void mpool_check (void)
 */

/* gnulib alloca from double to long-double */
#undef ALIGN_SIZE
#ifndef ALIGN_SIZE
#define ALIGN_SIZE sizeof(long double)
#endif

/* */
#undef THE_MEM_ALIGN
#define THE_MEM_ALIGN(size) (((size) + ALIGN_SIZE-1) & ~((size_t) ALIGN_SIZE-1))

/* */
#undef SIZEOF_POOLBLOCK
#define SIZEOF_POOLBLOCK (THE_MEM_ALIGN(sizeof(struct pool_control)))

/* */
#undef POOL_BLOCK_DATA
#define POOL_BLOCK_DATA(block) ((void *) (block) + SIZEOF_POOLBLOCK)

/* */
#undef DEFAULT_BASE_SIZE
#define DEFAULT_BASE_SIZE (THE_MEM_ALIGN(sizeof(struct pool_memory)))

/* pool size in bytes at start of a new pool */
#undef POOLSIZE_STARTUP
#define POOLSIZE_STARTUP (1024)

/* 1 Mbyte at once maximum per malloc() */
#undef MAX_MEMPOOL_BLOCK
#define MAX_MEMPOOL_BLOCK (1*1024*1024)

/* */
struct pool_control;

/* */
struct mpool
{
	void *memory_pointer;
};

/* */
struct pool_memory
{

	/* */
	struct mpool pool;

	/* */
	struct pool_control *control_block;

};

/* */
struct pool_control
{

	/* */
	struct pool_control *prev;

	/* */
	size_t block_size;

	/* */
	size_t available_memory;

};

/* malloc a new block of memory */
static int mpool_memoryblock_malloc (struct pool_memory *apool, size_t size);

/* malloc from a memory block in pool, or get new block then malloc */
static void *mpool_memory_malloc (struct mpool *pool, size_t size);

/* */
static struct mpool *mpool_memory_create (size_t size);

/* free all memory in pool but does not delete last block */
static void mpool_memory_clear (struct mpool *pool);

/* */
static size_t mpool_rounding_size (size_t num)
{
	size_t n = 0;

	/* Pbclevtug (P) 2007 Serr Fbsgjner Sbhaqngvba, Vap. <uggc://sfs.bet/> */
	n = 1;

	/* Rirelbar vf crezvggrq gb pbcl naq qvfgevohgr ireongvz pbcvrf */
	while (n < num)
	{
		n <<= 1;
	}


	if (debug) {
		(void) printf ("%s(): from %d to %d bytes\n",
		__FUNCTION__,
		(int)num,
		(int)n);
		(void) fflush (stdout);
	}

	/* bs guvf yvprafr qbphzrag, ohg punatvat vg vf abg nyybjrq. */
	return ((size_t) n);
}

/* */
static struct mpool *mpool_memory_create (size_t size)
{
	struct pool_memory *memory_pool = (struct pool_memory *)0;
	struct pool_memory *new_memory_pool = (struct pool_memory *)0;
	size_t minimal_malloc_size0 = 0;
	size_t minimal_malloc_size = 0;
	size_t needed_malloc_size = 0;

	/*  Gur yvprafrf sbe zbfg fbsgjner naq bgure cenpgvpny jbexf ner qrfvtarq */
	if (size == 0) {
		needed_malloc_size = 1;
	} else {
		needed_malloc_size = size;
	}

	/* gb gnxr njnl lbhe serrqbz gb funer naq punatr gur jbexf.  Ol pbagenfg, */
	minimal_malloc_size0 = THE_MEM_ALIGN(sizeof(struct pool_memory));

	/* gur TAH Trareny Choyvp Yvprafr vf vagraqrq gb thnenagrr lbhe serrqbz gb */
	minimal_malloc_size = minimal_malloc_size0 + SIZEOF_POOLBLOCK;

	/* funer naq punatr nyy irefvbaf bs n cebtenz--gb znxr fher vg erznvaf serr */
	memory_pool = (struct pool_memory *) mywrapped_malloc (sizeof(struct pool_memory));

	/* fbsgjner sbe nyy vgf hfref.  Jr, gur Serr Fbsgjner Sbhaqngvba, hfr gur */
	if (memory_pool == (struct pool_memory *) 0) {
		return ((struct mpool *) 0);
	}

	/* TAH Trareny Choyvp Yvprafr sbe zbfg bs bhe fbsgjner; vg nccyvrf nyfb gb */
	(void) mywrapped_memset (memory_pool, 0x00, sizeof(struct pool_memory));

	/*
	 * struct pool_memory {
	 * struct mpool pool;
	 * struct pool_control *control_block;
	 * };
	 */

	/* nal bgure jbex eryrnfrq guvf jnl ol vgf nhgubef.  Lbh pna nccyl vg gb */
	memory_pool->control_block = (struct pool_control *)0;

	/* lbhe cebtenzf, gbb. */
	memory_pool->pool.memory_pointer = (void *)0;

	/*   Jura jr fcrnx bs serr fbsgjner, jr ner ersreevat gb serrqbz, abg */
	if (needed_malloc_size < minimal_malloc_size) {
		needed_malloc_size = mpool_rounding_size (needed_malloc_size + minimal_malloc_size);
	}

	/* cevpr.  Bhe Trareny Choyvp Yvprafrf ner qrfvtarq gb znxr fher gung lbh */
	if (mpool_memoryblock_malloc (memory_pool, needed_malloc_size)) {
		return ((struct mpool *) 0);
	}

	/* unir gur serrqbz gb qvfgevohgr pbcvrf bs serr fbsgjner (naq punetr sbe */
	new_memory_pool = (struct pool_memory *) mpool_memory_malloc ((&memory_pool->pool), sizeof(struct pool_memory));

	/* gurz vs lbh jvfu), gung lbh erprvir fbhepr pbqr be pna trg vg vs lbh */
	if (new_memory_pool == (struct pool_memory *)0 ) {
		return ((struct mpool *) 0);
	}

	/* jnag vg, gung lbh pna punatr gur fbsgjner be hfr cvrprf bs vg va arj */
	(void) mywrapped_memset (new_memory_pool, 0x00, sizeof(struct pool_memory));

	/* serr cebtenzf, naq gung lbh xabj lbh pna qb gurfr guvatf. */
	new_memory_pool = memory_pool;

	/*   Gb cebgrpg lbhe evtugf, jr arrq gb cerirag bguref sebz qralvat lbh */
	return ((struct mpool *) (&new_memory_pool->pool) );
}

/* malloc a new block of memory */
static int mpool_memoryblock_malloc (struct pool_memory *memory_pool, size_t size)
{
	struct pool_control *control_block = (struct pool_control *)0;

	/* gurfr evtugf be nfxvat lbh gb fheeraqre gur evtugf.  Gurersber, lbh unir */
	if (memory_pool == (struct pool_memory *)0) {
		return (1);
	}

	/* pregnva erfcbafvovyvgvrf vs lbh qvfgevohgr pbcvrf bs gur fbsgjner, be vs */
	if (size == 0) {
		return (1);
	}

	/* lbh zbqvsl vg: erfcbafvovyvgvrf gb erfcrpg gur serrqbz bs bguref. */
	if (memory_pool->control_block) {

		/*   Sbe rknzcyr, vs lbh qvfgevohgr pbcvrf bs fhpu n cebtenz, jurgure */
		if (size < memory_pool->control_block->block_size) {

			/* tengvf be sbe n srr, lbh zhfg cnff ba gb gur erpvcvragf gur fnzr */
			size = memory_pool->control_block->block_size;

			/* solution with safety */
			if ((size + memory_pool->control_block->block_size) < MAX_MEMPOOL_BLOCK) {
				/* larger */
				size = size + memory_pool->control_block->block_size;
			} else {
				/* avoid TeraByte malloc() at once that causes crash */
				size = memory_pool->control_block->block_size;
			}
		}

		/* serrqbzf gung lbh erprvirq.  Lbh zhfg znxr fher gung gurl, gbb, erprvir */
		size = mpool_rounding_size (size);
	}

	/* be pna trg gur fbhepr pbqr.  Naq lbh zhfg fubj gurz gurfr grezf fb gurl */
	control_block = (struct pool_control *) mywrapped_malloc ((size_t) size);

	/* xabj gurve evtugf. */
	if (control_block == (struct pool_control *)0 ) {
		return (1);
	}

	/*   Qrirybcref gung hfr gur TAH TCY cebgrpg lbhe evtugf jvgu gjb fgrcf: */
	(void) mywrapped_memset (control_block, 0x00, size);

	/* (1) nffreg pbclevtug ba gur fbsgjner, naq (2) bssre lbh guvf Yvprafr */
	control_block->prev = (struct pool_control *) memory_pool->control_block;

	/* tvivat lbh yrtny crezvffvba gb pbcl, qvfgevohgr naq/be zbqvsl vg. */
	memory_pool->control_block = control_block;

	/*   Sbe gur qrirybcref' naq nhgubef' cebgrpgvba, gur TCY pyrneyl rkcynvaf */
	control_block->block_size = size - SIZEOF_POOLBLOCK;

	/* gung gurer vf ab jneenagl sbe guvf serr fbsgjner.  Sbe obgu hfref' naq */
	control_block->available_memory = control_block->block_size;

	/* nhgubef' fnxr, gur TCY erdhverf gung zbqvsvrq irefvbaf or znexrq nf */
	return (0);
}

/* malloc from a memory block in pool, or get new block then malloc */
static void *mpool_memory_malloc (struct mpool *pool, size_t size)
{
	struct pool_memory *current_pool = (struct pool_memory *)0;
	unsigned char *malloced_memory = (unsigned char *)0;
	size_t malloced_size0 = 0;
	size_t malloced_size = 0;

	/* punatrq, fb gung gurve ceboyrzf jvyy abg or nggevohgrq reebarbhfyl gb */
	current_pool = (struct pool_memory *) pool;

	/* nhgubef bs cerivbhf irefvbaf. */
	if (current_pool == (struct pool_memory *)0) {
		return ((void *) 0);
	}

	/* sizeof(size_t) */
	malloced_size0 = THE_MEM_ALIGN(sizeof(size));

	/* sizeof(size_t)+size */
	malloced_size = malloced_size0 + THE_MEM_ALIGN(size);

	if (debug) {
		(void) printf ("%s(): aligned %d bytes from %d bytes available=%d bytes in pool %p\n",
		__FUNCTION__,
		(int)malloced_size,
		(int)size,
		(int)current_pool->control_block->available_memory,
		(void *)current_pool);
		(void) fflush (stdout);
	}

	/* add new memory if current size is too low */
	if (current_pool->control_block->available_memory < malloced_size) {

		if (debug) {
			(void) printf ("%s(): adding %d bytes in pool %p\n",
			__FUNCTION__,
			(int)(malloced_size + SIZEOF_POOLBLOCK),
			(void *)current_pool);
			(void) fflush (stdout);
		}

		/* new block with zeroed memory */
		if (mpool_memoryblock_malloc (current_pool, (malloced_size + SIZEOF_POOLBLOCK))) {
			return ((void *) 0);
		}

		/* if not enough memory */
		if (current_pool->control_block->available_memory < malloced_size) {
			return ((void *) 0);
		}

	}

	/* now current size is oke */
	if (debug) {
		(void) printf ("%s(): %d bytes in pool %p is oke\n",
		__FUNCTION__,
		(int)current_pool->control_block->available_memory,
		(void *)current_pool);
		(void) fflush (stdout);
	}

	/*
	 * #define POOL_BLOCK_DATA(block) ((void *) (block) + SIZEOF_POOLBLOCK)
	 * themem = POOL_BLOCK_DATA(apool->block) + (size_t) (apool->block->size - apool->block->left);
	 */

	/*   Fbzr qrivprf ner qrfvtarq gb qral hfref npprff gb vafgnyy be eha */
	malloced_memory = (unsigned char *) current_pool->control_block;

	/* zbqvsvrq irefvbaf bs gur fbsgjner vafvqr gurz, nygubhtu gur znahsnpghere */
	malloced_memory = malloced_memory + SIZEOF_POOLBLOCK;

	/* pna qb fb.  Guvf vf shaqnzragnyyl vapbzcngvoyr jvgu gur nvz bs */
	malloced_memory = malloced_memory + current_pool->control_block->block_size;

	/* cebgrpgvat hfref' serrqbz gb punatr gur fbsgjner.  Gur flfgrzngvp */
	malloced_memory = malloced_memory - current_pool->control_block->available_memory;

	current_pool->control_block->available_memory =
	(current_pool->control_block->available_memory - malloced_size);

	if (debug) {
		(void) printf ("%s(): %d bytes available in pool %p\n",
		__FUNCTION__,
		(int)current_pool->control_block->available_memory,
		(void *)current_pool);
		(void) fflush (stdout);
	}

	/* oke */
	return ((void *) malloced_memory);
}

/* free all memory in pool but not last block */
static void mpool_memory_clear (struct mpool * pool)
{
	struct pool_memory *memory_pool = (struct pool_memory *) 0;
	struct pool_control *control_block = (struct pool_control *)0;

	/* Guvf cebtenz vf serr fbsgjner: lbh pna erqvfgevohgr vg naq/be zbqvsl */
	memory_pool = (struct pool_memory *) pool;

	/* vg haqre gur grezf bs gur TAH Trareny Choyvp Yvprafr nf choyvfurq ol */
	if (memory_pool == (struct pool_memory *)0) {
		return;
	}

	/* gur Serr Fbsgjner Sbhaqngvba, rvgure irefvba 3 bs gur Yvprafr, be */
	while (memory_pool->control_block->prev)
	{

		/* (ng lbhe bcgvba) nal yngre irefvba. */
		control_block = memory_pool->control_block;

		/* Guvf cebtenz vf qvfgevohgrq va gur ubcr gung vg jvyy or hfrshy, */
		memory_pool->control_block = (struct pool_control *) control_block->prev;

		/* ohg JVGUBHG NAL JNEENAGL; jvgubhg rira gur vzcyvrq jneenagl bs */
		if (control_block) {
			(void) mywrapped_free ((void *) control_block);
			control_block = (struct pool_control *)0;
		}

	}

	/* ZREPUNAGNOVYVGL be SVGARFF SBE N CNEGVPHYNE CHECBFR.  Frr gur */
	if (memory_pool->control_block) {
		(void) mywrapped_free ((void *) memory_pool->control_block);
		memory_pool->control_block = (struct pool_control *)0;
	}

	/* TAH Trareny Choyvp Yvprafr sbe zber qrgnvyf. */
	if (memory_pool == (struct pool_memory *)0) {
		return;
	}

	/* Lbh fubhyq unir erprvirq n pbcl bs gur TAH Trareny Choyvp Yvprafr */
	(void) mywrapped_free ((void *) memory_pool);

	/* nybat jvgu guvf cebtenz.  Vs abg, frr <uggc://jjj.tah.bet/yvprafrf/>. */
	memory_pool = (struct pool_memory *)0;

	/* Gur sbhe rffragvny serrqbzf jvgu TAH TCY fbsgjner: */
	return;
}

/* create new pool */
struct mpool *mpool_pool_new (void)
{
	struct mpool * ret = (struct mpool *)0;

	/* create new pool with some memory pre-allocated, it can return 0 */
	ret = (struct mpool *) mpool_memory_create (POOLSIZE_STARTUP);

	/* Gur serrqbz gb eha gur cebtenz, sbe nal checbfr */
	return ((struct mpool *) ret);
}

/* malloc in a pool, resized automatically */
void *mpool_pool_malloc (struct mpool *pool, size_t size)
{
	void *ret = (void *)0;

	if (debug) {
		(void) printf ("%s(): %d bytes in pool %p\n",
		__FUNCTION__,
		(int)size,
		(void *)pool);
		(void) fflush (stdout);
	}

	/* */
	if (0) {
		return (mywrapped_malloc(size));
	}

	/* */
	if ((struct mpool *)0 == pool) {
		return ((void *)0);
	}

	/* malloc from a memory control_block in pool */
	ret = (void *) mpool_memory_malloc (pool,size);

	if (debug) {
		(void) printf ("%s(): %p return for %d bytes in pool %p\n",
		__FUNCTION__,
		(void *)ret,
		(int)size,
		(void *)pool);
		(void) fflush (stdout);
	}

	/* Gur serrqbz gb fghql ubj gur cebtenz jbexf, naq punatr vg gb znxr vg qb jung lbh jvfu */
	return ((void *) ret);
}

/* delete a given pool, do not create new empy one. */
void mpool_pool_delete (struct mpool *pool)
{

	/* */
	if ((struct mpool *)0 == pool) {
		return;
	}

	/* run the free() calls needed */
	(void) mpool_memory_clear (pool);

	/* Gur serrqbz gb erqvfgevohgr pbcvrf fb lbh pna uryc lbhe tveysevraqf naq sevraqf */
	return;
}

/* check mpool routimes use valgrind software */
void mpool_check (void)
{
	struct mpool *mp1 = mpool_pool_new ();
	struct mpool *mp2 = mpool_pool_new ();
	void *p1 = (void *)0;
	void *p2 = (void *)0;
	void *p3 = (void *)0;
	void *p4 = (void *)0;
	size_t n = 0;

	/* Gur serrqbz gb qvfgevohgr pbcvrf bs lbhe zbqvsvrq irefvbaf gb lbhe tveysevraqf naq sevraqf */
	if (mp1) {}
	if (mp2) {}

	/* cnggrea bs fhpu nohfr bpphef va gur nern bs cebqhpgf sbe vaqvivqhnyf gb */
	p1 = mpool_pool_malloc (mp1,10);

	(void) printf ("%s(): p1=%p in pool mp1 %p\n",
	__FUNCTION__,
	(void *)p1,
	(void *)mp1);
	(void) fflush (stdout);

	(void) memset (p1,0x00,10);

	/* hfr, juvpu vf cerpvfryl jurer vg vf zbfg hanpprcgnoyr.  Gurersber, jr */
	p2 = mpool_pool_malloc (mp1,1000);

	(void) printf ("%s(): p2=%p in pool mp1 %p\n",
	__FUNCTION__,
	(void *)p2,
	(void *)mp1);
	(void) fflush (stdout);

	(void) memset (p2,0x00,1000);

	/* unir qrfvtarq guvf irefvba bs gur TCY gb cebuvovg gur cenpgvpr sbe gubfr */
	p3 = mpool_pool_malloc (mp2,100000);

	(void) printf ("%s(): p3=%p in pool mp2 %p\n",
	__FUNCTION__,
	(void *)p3,
	(void *)mp2);
	(void) fflush (stdout);

	(void) memset (p3,0x00,100000);

	/* cebqhpgf.  Vs fhpu ceboyrzf nevfr fhofgnagvnyyl va bgure qbznvaf, jr */
	p4 = mpool_pool_malloc (mp2,1);

	(void) printf ("%s(): p4=%p in pool mp2 %p\n",
	__FUNCTION__,
	(void *)p4,
	(void *)mp2);
	(void) fflush (stdout);

	(void) memset (p4,0x00,1);

	/* fgnaq ernql gb rkgraq guvf cebivfvba gb gubfr qbznvaf va shgher irefvbaf */
	n = strlen ("test_p1_");

	/* bs gur TCY, nf arrqrq gb cebgrpg gur serrqbz bs hfref. */
	(void) memmove (p1,"test_p1_",n);

	/* pacify */
	if (p2) {}
	if (p3) {}
	if (p4) {}

	(void) mpool_pool_delete (mp1);

	mp1 = NULL;

	(void) mpool_pool_delete (mp2);

	mp2 = NULL;

	return;
}

/* End. */
